Web components are the next step in building modern web applications and a great way to prototype accessible widgets.
Today we are going to build a disclosure widget, by extending the native HTML button element.
Web components are a collection of several standards (Templates, Shadow DOM, Custom elements, Imports) that allow us to create reusable elements and encapsulate the complexity of code and style into the component.
The support for Web components varies amongst browsers. So in order to use it now we are going to use the Polymer library.
Registering the component.
Before using our component, we need to register it. For usability and
clean separation, we are going to declare our component in a separate
file: w3c-disclosure.html
The first thing we need to add in our file, is to import the polymer library, in order to enable the shadow DOM, custom elements, …
<link rel="import" href="components/polymer/polymer.html">
Then we declare the component like this:
<polymer-element name="w3c-disclosure" extends="button">
- The element name for the component, (‘w3c-disclosure’) must always contain a dash (-).
- The
extends
directive indicates that the component is an extension of the existing HTML button element .
We also create a base template structure for the shadow DOM subtree, and a JavaScript constructor.
So now our component code looks like this:
<link rel="import" href="components/polymer/polymer.html">
<polymer-element name="w3c-disclosure" extends="button">
<template>
</template>
<script>
Polymer('w3c-disclosure', {
ready: function() {
},
});
</script>
</polymer-element>
Using the component
On the page where the component will be used, you need to load the
Polymer platform library before any other JavaScript library, in the <head>
section.
<script src="components/platform/platform.js"></script>
Then we need to import the component description.
<link rel="import" href="w3c-disclosure.html">
Now we can use our component in the page like any HTML element:
<button is="w3c-disclosure">Expand section 1</button>
Custom elements that inherit from native elements are called type extension custom elements. They inherit from a specialized version of HTMLElement and that means it can’t be used directly as a tag (<w3c-disclosure></w3c-disclosure>
), instead we need to use the ‘is
’ syntax.
Adding accessibility logic and style.
So far our component does nothing more than a regular button. So we are now going to see how to extend the button behaviour to add our disclosure feature.
In the components declaration use the ‘attributes’ directive to
declare added public attributes. For our button we are going to add two
attributes: controlfor
and expanded
<polymer-element name="w3c-disclosure" extends="button"
attributes="controlfor expanded" on-click="{{buttonClick}}">
controlfor
, references the disclosed sectionid
expanded
, if present, the section will be expanded on page loadon-click
attribute is bound to the ‘click
’ event so thebuttonClick
function is called on each button click event
Now we update the component in the page, to use those attributes.
<button is="w3c-disclosure" controlfor="details1">Expand section 1</button>
<section id="details1">
Lorem ipsum …
</section>
Next we need to add the button logic, into the Web Component JavaScript.
The ready
function: is the component constructor. Called on the WebComponentsReady
event.
With this function we initialize states and an aria attribute when the component is loaded.
ready: function() {
// If the expanded attribute is present, then the initial state is expanded.
if (this.expanded === null) this.expanded = false;
else this.expanded = true;
// We map the controlfor attribute to the aria-controls attribute.
this.setAttribute('aria-controls', this.controlfor);
// We set a pointer to the disclosed content
this.target = document.querySelector('#' + this.controlfor);
},
The buttonClick
function: is called on every ‘click
’ event.
When the button is clicked, we toggle the expanded state.
buttonClick: function(event, detail, sender) {
this.expanded = !this.expanded;
},
The expandedChanged
function: is called when the expanded variable has been updated.
The Polymer data-binding feature automatically links the ‘expanded
’ attribute to expandedChanged
function so the function is called when the value for the variable has changed.
In this function we set aria states, and toggle the disclosed content.
expandedChanged: function(oldValue, newValue) {
// We map the expanded value to the aria-expanded state
this.setAttribute('aria-expanded', newValue);
// We hide/show the content using the hidden attribute
this.target.hidden = !newValue;
//For backward compatibility, we also set the CSS display attribute
if (newValue) { // expanded is true
this.target.style.display='block';
} else {
this.target.style.display='none';
}
},
Styling
The widget has now all the logic implemented, and we are going to add a little icon to visually indicate the expanded state.
In the template area, we add the following CSS rules:
<style type="text/css">
:host #icon:before {
content:"?"; font-size: 1em;
}
:host #icon.expanded:before {
content:"?"; font-size: 1em;
}
</style>
Many other techniques can be used for displaying icons. We used this one, with the ‘content
’ attribute, for simplicity, in our demo.
The :host
element represents the custom element itself, and allows the component to be styled from outside.
One last little trick: when your page is hosted online, you may notice some delay, for the disclosure content, to be hidden, when the page loads. It is due to the time the browser needs to load Polymer and apply the component polyfill.
For this issue Polymer provides a solution that hides the page content, until all elements are loaded. Add the ‘unresolved
’ attribute to body:
<body unresolved>
Example use of w3c-disclosure button
Multiple w3c-disclosure buttons could be used in a list to allow the user to collapse a set of fields down to a small set of headings, with the ability to open each one.
In conclusion.
If you already know HTML and JavaScript, Web Components are easy to create and give us a great way to extend and improve regular HTML elements. And thanks to the Polymer or X-tags polyfill libraries, you can use it right now to create out-of-the-box accessible widgets, or improve existing Web Components by adding ARIA roles and attributes.